package vasys.mehaniksspacemars;

import org.bukkit.*;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.generator.WorldInfo;
import vasys.mehaniksspacecore.MehaniksSpaceCore;

import java.util.*;

public class CustomChunkGenerator extends ChunkGenerator {

    public static final FastNoiseLite terrainNoise = new FastNoiseLite();
    public static final FastNoiseLite addedTerrainNoise = new FastNoiseLite();
    private final FastNoiseLite detailNoise = new FastNoiseLite();
    public static final FastNoiseLite detailTerrainNoise = new FastNoiseLite();
    //public static final FastNoiseLite structureNoise = new FastNoiseLite();


    private final HashMap<Integer, List<Material>> layers = new HashMap<Integer, List<Material>>() {{
        put(0, Arrays.asList(Material.RED_SAND, Material.SMOOTH_RED_SANDSTONE, Material.TUFF, Material.ORANGE_TERRACOTTA, Material.TERRACOTTA));
        put(1, Arrays.asList(Material.TUFF, Material.TERRACOTTA, Material.SMOOTH_RED_SANDSTONE));
        put(2, Arrays.asList(Material.IRON_ORE, Material.REDSTONE_ORE, Material.RAW_IRON_BLOCK, Material.COPPER_ORE, Material.RAW_COPPER_BLOCK));
        put(8, Arrays.asList(Material.BASALT, Material.SMOOTH_BASALT, Material.COBBLED_DEEPSLATE));
        put(4, Arrays.asList(Material.TUFF, Material.COBBLESTONE, Material.ANDESITE));
        put(3, Arrays.asList(Material.BEDROCK));
        put(5, Arrays.asList(Material.DEEPSLATE_DIAMOND_ORE, Material.DEEPSLATE_COAL_ORE));
        put(6, Arrays.asList(Material.BLACKSTONE, Material.OBSIDIAN, Material.DEEPSLATE_COAL_ORE));
        put(7, Arrays.asList(Material.LAVA, Material.MAGMA_BLOCK));
    }};

    public CustomChunkGenerator() {

        // Set frequencies, lower frequency = slower change.
        terrainNoise.SetFrequency(0.001f);
        addedTerrainNoise.SetFrequency(0.003f);
        detailNoise.SetFrequency(0.02f);
        detailTerrainNoise.SetFrequency(0.005f);
        //structureNoise.SetFrequency(0.08f);

        terrainNoise.SetNoiseType(FastNoiseLite.NoiseType.Perlin);
        // Fractal pattern (optional).
        terrainNoise.SetFractalType(FastNoiseLite.FractalType.FBm);
        terrainNoise.SetFractalOctaves(5);

        addedTerrainNoise.SetFractalType(FastNoiseLite.FractalType.PingPong);
        addedTerrainNoise.SetFractalOctaves(5);
        addedTerrainNoise.SetFractalWeightedStrength(0);
        addedTerrainNoise.SetFractalPingPongStrength(2);

        detailNoise.SetFractalType(FastNoiseLite.FractalType.PingPong);
        detailNoise.SetFractalPingPongStrength(1.8f);
        detailNoise.SetFractalOctaves(5);

        //structureNoise.SetNoiseType(FastNoiseLite.NoiseType.Value);

        terrainNoise.SetSeed(MehaniksSpaceCore.seed);
        addedTerrainNoise.SetSeed(MehaniksSpaceCore.seed);
        detailNoise.SetSeed(MehaniksSpaceCore.seed);
        detailTerrainNoise.SetSeed(MehaniksSpaceCore.seed);
    }

    @Override
    public void generateBedrock(WorldInfo worldInfo, Random random, int chunkX, int chunkZ, ChunkData chunkData) {
        for(int y = chunkData.getMinHeight(); y < -60 && y < chunkData.getMaxHeight(); y++) {
            for (int x = 0; x < 16; x++) {
                for (int z = 0; z < 16; z++) {
                    if(-63 + (2 * random.nextInt())> y) {
                        chunkData.setBlock(x, y, z, layers.get(3).get(random.nextInt(layers.get(3).size())));
                    }
                    if(-63 > y) {
                        chunkData.setBlock(x, y, z, layers.get(3).get(random.nextInt(layers.get(3).size())));
                    }
                }
            }
        }
    }

    @Override
    public void generateNoise(WorldInfo worldInfo, Random random, int chunkX, int chunkZ, ChunkData chunkData) {
        for(int y = chunkData.getMinHeight(); y < 320 && y < chunkData.getMaxHeight(); y++) {
            for(int x = 0; x < 16; x++) {
                for(int z = 0; z < 16; z++) {
                    float noise2 = (terrainNoise.GetNoise(x + (chunkX * 16), z + (chunkZ * 16)) * 2) + (detailTerrainNoise.GetNoise(x + (chunkX * 16), z + (chunkZ * 16)) / 10);
                    //float noise2 = (structureNoise.GetNoise(x + (chunkX * 16), z + (chunkZ * 16)) * 2) + (detailTerrainNoise.GetNoise(x + (chunkX * 16), z + (chunkZ * 16)) / 10);
                    float noise3 = detailNoise.GetNoise(x + (chunkX * 16), y, z + (chunkZ * 16));
                    float noise4 = (addedTerrainNoise.GetNoise(x + (chunkX * 16), z + (chunkZ * 16)) * 2) + (detailTerrainNoise.GetNoise(x + (chunkX * 16), z + (chunkZ * 16)) / 10);
                    float currentY = (224 + ((noise2 * 32) + (noise4 * 2)));
                    float currentY1 = (64 + ((noise2 * 32) + (noise4)));


                    if(currentY1 > y) {
                        Material neighbour1 = layers.get(8).get(Math.min(random.nextInt(layers.get(8).size()), random.nextInt(layers.get(8).size())));
                        List<Material> neighbourBlocks1 = new ArrayList<Material>(Arrays.asList(chunkData.getType(Math.max(x - 1, 0), y, z), chunkData.getType(x, Math.max(y - 1, 0), z), chunkData.getType(x, y, Math.max(z - 1, 0)))); // A list of all neighbour blocks.
                        float distanceToSurface1 = Math.abs(y - currentY);
                        double function1 = .1 * Math.pow(distanceToSurface1, 2) - 1;

                        if(noise3 > Math.min(function1, -.3)) {
                            if (random.nextFloat() < -0.005 * (64-y)/128) {
                                neighbour1 = layers.get(5).get(Math.min(random.nextInt(layers.get(5).size()), random.nextInt(layers.get(5).size()))); // A basic way to shift probability to lower values.
                            }
                            if (random.nextFloat() < -0.03 * (64-y)/128) {
                                neighbour1 = layers.get(6).get(Math.min(random.nextInt(layers.get(6).size()), random.nextInt(layers.get(6).size()))); // A basic way to shift probability to lower values.
                            }
                            if ((!Collections.disjoint(neighbourBlocks1, layers.get(5))) & (!Collections.disjoint(neighbourBlocks1, layers.get(6)))) {
                                for (Material neighbourBlock1 : neighbourBlocks1) {
                                    if (layers.get(5).contains(neighbourBlock1) && random.nextFloat() < -0.01 * layers.get(5).indexOf(neighbourBlock1) + 0.4) {
                                        neighbour1 = neighbourBlock1;
                                    }
                                }
                            }
                            chunkData.setBlock(x, y, z, neighbour1);
                        }
                        else if(y < -32) {
                            chunkData.setBlock(x, y, z, layers.get(7).get(random.nextInt(layers.get(7).size())));
                        }
                    }
                    else if(y < currentY) {
                        Material neighbour = layers.get(4).get(Math.min(random.nextInt(layers.get(4).size()), random.nextInt(layers.get(4).size()))); // A basic way to shift probability to lower values.
                        List<Material> neighbourBlocks = new ArrayList<Material>(Arrays.asList(chunkData.getType(Math.max(x - 1, 0), y, z), chunkData.getType(x, Math.max(y - 1, 0), z), chunkData.getType(x, y, Math.max(z - 1, 0)))); // A list of all neighbour blocks.
                        float distanceToSurface = Math.abs(y - currentY);
                        double function = .1 * Math.pow(distanceToSurface, 2) - 1;
                        // Set grass if the block closest to the surface.
                        if(noise3 > Math.min(function, -.3)) {
                            if (distanceToSurface < 4) {
                                chunkData.setBlock(x, y, z, layers.get(0).get(random.nextInt(layers.get(0).size())));
                            } else if (distanceToSurface < 10) {
                                chunkData.setBlock(x, y, z, layers.get(1).get(random.nextInt(layers.get(1).size())));
                            } else {
                                // Randomly place vein anchors.
                                if (random.nextFloat() < 0.01 * (224-y)/160) {
                                    neighbour = layers.get(2).get(Math.min(random.nextInt(layers.get(2).size()), random.nextInt(layers.get(2).size()))); // A basic way to shift probability to lower values.
                                }

                                // If the current block has an ore block as neighbour, try the current block.
                                if ((!Collections.disjoint(neighbourBlocks, layers.get(2))) & (!Collections.disjoint(neighbourBlocks, layers.get(4)))) {
                                    for (Material neighbourBlock : neighbourBlocks) {
                                        if (layers.get(2).contains(neighbourBlock) && random.nextFloat() < -0.01 * layers.get(2).indexOf(neighbourBlock) + 0.4) {
                                            neighbour = neighbourBlock;
                                        }
                                    }
                                }
                                chunkData.setBlock(x, y, z, neighbour);
                            }
                        }
                    }
                }
            }
        }
    }
}